package domain;
import java.io.*;
import java.net.*;
import javax.net.*;
import javax.net.ssl.*;
import java.security.*;
import java.util.Observable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;


/**
 * This class takes care of HTTPS related requests.<br>For handling HTTP related requests, please see the documentation for the DragonEngine class.
 * @author HTTPDragon Team
 */
    @SuppressWarnings("LoggerStringConcat")
public class DragonEngineS extends Observable implements Runnable
{
    private static final String SERVICENAME = "(HTTPS) ";
    private static final Logger logger = Logger.getLogger("HTTPDragon Log");
    private ExecutorService executor;
    private SSLServerSocket serverSocket;
    private boolean running;
    private String keystore;
    private char[] sslPass;
    private int port;
    private ServerConfig config;

    /**
     * Public Constructor for creating the DragonEngineS
     * @param config a server configuration.
     * @param sslPass the SSL password. To enter the password you must pass it as an argument.
     */
    public DragonEngineS(ServerConfig config, char[] sslPass)
    {
        setPort(config.getHttpsPort());
        setConfig(config);
        setSslPass(sslPass);
        setKeystore(config.getHttpsPrivateKey());  
    }
    
    /**
     * This method determines whether or not the HTTPS module is running.
     * @return true or false.
     */  
    public boolean isRunning(){
        return running;
    } 

    /**
     * This method sets the SSL keystore.
     * @param keystore the keystore string.
     */
    private void setKeystore(String keystore) {
        this.keystore = keystore;
    }
    
    /**
     * This method sets the SSL password.
     * @param sslPass a character array containing the password.
     */
    private void setSslPass(char[] sslPass) {
        this.sslPass = sslPass;
    }
    
    /**
     * This method gets the current HTTPS port number.
     * @return The HTTPS port number.
     */
    public int getPort() {
        return port;
    }
    
    /**
     * This method sets the HTTPS port number.
     * @param Port the desired port number
     */
    private void setPort(int Port) {
        this.port = Port;
    }

    /**
     * This method assigns a configuration to the HTTPS module.
     * @param Config a server configuration.
     */
    private void setConfig(ServerConfig Config){
        this.config = Config;
    }
    
    /**
     * 
     * @return ServerSocket
     * @throws Exception 
     */
    public ServerSocket getServer() throws Exception {  
        
        KeyStore ks = KeyStore.getInstance("JKS");
        ks.load(new FileInputStream(keystore),sslPass);
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(ks,sslPass);
        SSLContext sslcontext = SSLContext.getInstance("SSLv3");
        sslcontext.init(kmf.getKeyManagers(),null,null);
        ServerSocketFactory asf = sslcontext.getServerSocketFactory();
        serverSocket = (SSLServerSocket)asf.createServerSocket(port);
        return serverSocket;
    }
    /**
     * This method starts the HTTPS module, so it can start processing requests.<br>Does not process HTTP requests.
     */
    @Override
    public void run() {
        try{
            if(config.isHttpsRun()){
                    ServerSocket ssocket = getServer();
                    setRunningTrue();
                    logger.info(SERVICENAME+"Service started, html source folder at " + config.getHttpsRootPath());
                    logger.info(SERVICENAME+"Default Index name has been set to " + config.getHttpsDefaultPage());            
                    logger.info(SERVICENAME+"Listening at port " + getPort() + " ...");
                    executor = Executors.newCachedThreadPool();
                while(true)
                {
                    Socket socket = ssocket.accept();
                    // nieuwe clienthandler thread op executorservice zetten voor uitvoering
                    executor.execute(new ClientHandler(socket, config));
                }
            }else{
                setRunningFalse();
            }        
        }
        catch(SocketException ex){
                switch (ex.toString()) {
                    case "java.net.SocketException: socket closed":
                        //nothing because 'Socket closed' exception is expected by us on shutdown()                    
                        break;
                    case "java.net.SocketException: Connection reset by peer: socket write error":
                        // Connection was closed by the browser while requesting
                        logger.info(SERVICENAME+"Connection reset by peer.");
                        break;
                    case "java.net.SocketException: Software caused connection abort: socket write error":
                        logger.info(SERVICENAME+"Software caused a connection abort.");
                        break;
                    case "java.net.BindException: Address already in use: JVM_Bind":
                        logger.info(SERVICENAME+"Port "+port+" is already in use by an other program.");
                        running=false;
                        break;
                    default:
                        System.err.println(ex.getMessage());
                        break;
                }
            }
            catch (IOException ex) { 
                if(ex.toString().equals("java.io.IOException: Keystore was tampered with, or password was incorrect"))
                    logger.warning(SERVICENAME+ex.getMessage()+".");
                System.err.println(ex);
            }
            catch (Exception ex) {            
                System.err.println(ex);
            }
            
    }
    private void setRunningFalse(){
            running = false;
            this.setChanged();
            this.notifyObservers();    
    }
    private void setRunningTrue(){
            running = true;
            this.setChanged();
            this.notifyObservers();  
    }
    
    
    /**
     * This method stops the HTTPS module.<br>Does not stop the HTTP module.
     */
     public void shutdown(){
        try {
            if(running){
            executor.shutdown(); //huidige open clienthandler thread(s) afsluiten
            setRunningFalse();
            serverSocket.close(); //niet meer luisteren op opgegeven poort
            logger.info(SERVICENAME+"Stopped listening at port " + port);
            }
        } catch (IOException ex) {            
            System.err.println(ex);
        }
    } 
}
